package cn.felord.authorization.config;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerSecurityConfiguration;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;

import javax.annotation.Resource;
import java.security.KeyPair;
import java.util.HashMap;
import java.util.Map;

/**
 * The type Authorization server config.
 *
 * @author dax.
 * @version v1.0
 * @since 2018 /1/8 13:49
 */
@Configuration
@EnableAuthorizationServer
@ConfigurationProperties("hive.key")
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    private static final String AUTHORIZATION_CODE = "authorization_code";
    private static final String CLIENT_CREDENTIALS = "client_credentials";
    private static final String REFRESH_TOKEN = "refresh_token";
    private static final String PASSWORD = "password";
    private static final String IMPLICIT = "implicit";

    @Value("${hive.key.keyPath}")
    private String keyPath;
    @Value("${hive.key.keyAlias}")
    private String keyAlias;
    @Value("${hive.key.keyPass}")
    private String keyPass;

    @Resource
    @Qualifier("authenticationManagerBean")
    private AuthenticationManager authenticationManager;
    @Resource
    private AccountService accountService;


    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {

        clients.inMemory()
//                 定义一个客户端
                .withClient("trusted-app")
//                支持所有的授权方式
                .authorizedGrantTypes(AUTHORIZATION_CODE, CLIENT_CREDENTIALS, REFRESH_TOKEN, PASSWORD, IMPLICIT)
//               客户端权限  区别用户权限
                .authorities("ROLE_TRUSTED_CLIENT")
//                作用域
                .scopes("read", "write")
//                资源端
                .resourceIds(ResourceConstant.DEFAULT_RESOURCE)
//                 token有效期
                .accessTokenValiditySeconds(60000)
//                    refresh_token有效期  refresh_token必须在过期之前调用才能换新的token    通过  /oauth/token?grant_type=refresh_token&scope=read&client_id=trusted-app&client_secret=secret&refresh_token=有效的refresh token
                .refreshTokenValiditySeconds(90000)
//                password等模式需要需要客户端密码
                .secret("secret")
//                自动批准
                .autoApprove(true);
    }

    /**
     * @see AuthorizationServerSecurityConfiguration#configure(HttpSecurity)
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
//      控制resource server 中 设置的token-info-uri:  /oauth/token_key  这个url的访问权限  对应 上面的配置中的 authorities() 配置  多个 遵循spring 表达式
        security.tokenKeyAccess("isAnonymous() || hasAuthority('ROLE_TRUSTED_CLIENT')");
//        控制resource server 中 设置的token-info-uri: /oauth/check_token 这个url的访问权限
        security.checkTokenAccess("hasAuthority('ROLE_TRUSTED_CLIENT')");
//        允许表单提交
        security.allowFormAuthenticationForClients();

//       密码加密方式  for client secrets   对应上面的 secret() 加密
//        security.passwordEncoder(passwordEncoder);

    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
//       默认支持 token 刷新 参见  AuthorizationServerEndpointsConfigurer  所以DefaultTokenServices 不用配置
//       DefaultTokenServices defaultTokenServices=new DefaultTokenServices()

//                    密码认证必须配置Manager
        endpoints.authenticationManager(authenticationManager);
//        启用jwt转换器 并定制化
        endpoints.accessTokenConverter(accessTokenConverter());
//      用户选择数据库控制
        endpoints.userDetailsService(userDetailsService());
//        启用jwt
        endpoints.tokenStore(tokenStore());
    }

    /**
     * 自定义用户信息获取途径.
     *
     * @return the user details service
     */
    @Bean
    public UserDetailsService userDetailsService() {
        return name -> {
            // 通过用户名获取用户信息
            Account account = accountService.findByName(name);
            if (account != null) {
                // 创建spring security安全用户
                return new User(account.getName(), account.getPassword(),
                        AuthorityUtils.createAuthorityList(account.getRoles()));
            } else {
                throw new UsernameNotFoundException("用户[" + name + "]不存在");
            }
        };

    }

    /**
     * Access token converter jwt access token converter.
     *
     * @return the jwt access token converter
     */
    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter() {
            /***
             * 重写增强token方法,用于自定义一些token返回的信息
             */
            @Override
            public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
                String userName = authentication.getUserAuthentication().getName();
                // 与登录时候放进去的UserDetail实现类 获取用户信息
                User user = (User) authentication.getUserAuthentication().getPrincipal();
                //  自定义 token属性 用来携带信息
                final Map<String, Object> additionalInformation = new HashMap<>();
                additionalInformation.put("userName", userName);
                additionalInformation.put("roles", user.getAuthorities());
                ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInformation);
                return super.enhance(accessToken, authentication);
            }

        };
        // 测试用,资源服务使用相同的字符达到一个对称加密的效果,生产时候使用RSA非对称加密方式
//        accessTokenConverter.setSigningKey("123");



//        使用keytool 生成 RSA密钥  采取下面的配置

        ClassPathResource resource = new ClassPathResource(keyPath);
        char[] pem = keyPass.toCharArray();
        KeyStoreKeyFactory keyFactory = new KeyStoreKeyFactory(resource, pem);
        KeyPair keyPair = keyFactory.getKeyPair(keyAlias, pem);
        accessTokenConverter.setKeyPair(keyPair);

        return accessTokenConverter;
    }

    /**
     * jwt  token 储存器
     *
     * @return token store
     */
    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }
}
